﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Windows;
using System.Windows.Data;
using System.Windows.Interop;

namespace OmniKits.Windows.Interop
{
    public static class WindowsInterop
    {
        private static ConditionalWeakTable<Window, Lazy<HwndSource>> HwndSourceWeakTable = new ConditionalWeakTable<Window, Lazy<HwndSource>>();
        private static Lazy<HwndSource> MakeLazyHwndSource(Window window)
            => new Lazy<HwndSource>(() => (HwndSource)HwndSource.FromVisual(window));
        private static HwndSource EnsureHwndSource(Window window)
            => HwndSourceWeakTable.GetValue(window, MakeLazyHwndSource).Value;

        private static ConditionalWeakTable<Window, Lazy<HwndSourceHook>> HwndSourceHookWeakTable = new ConditionalWeakTable<Window, Lazy<HwndSourceHook>>();
        private static Lazy<HwndSourceHook> MakeLazyHwndSourceHook(Window window)
            => new Lazy<HwndSourceHook>(() =>
            {
                HwndSourceHook handler = (IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled) =>
                {
                    if (msg == User32.WM_DWMCOMPOSITIONCHANGED)
                    {
                        if (DwmApi.DwmIsCompositionEnabled())
                        {
                            RefreshIsWindowDisplayAffinityMonitor(window);
                        }
                    }

                    return IntPtr.Zero;
                };

                EnsureHwndSource(window).AddHook(handler);

                return handler;
            });

        private static HwndSourceHook EnsureHwndSourceHook(Window window)
            => HwndSourceHookWeakTable.GetValue(window, MakeLazyHwndSourceHook).Value;

        private static ConditionalWeakTable<Window, Lazy<EventHandler>> SourceInitializedWeakTable = new ConditionalWeakTable<Window, Lazy<EventHandler>>();
        private static Lazy<EventHandler> MakeLazySourceInitializedHandler(Window window)
            => new Lazy<EventHandler>(() =>
            {
                EventHandler handler = delegate
                {
                    var isHitTestTransparent = window.GetIsHitTestTransparent();
                    ApplyIsHitTestTransparent(window, isHitTestTransparent);

                    RefreshIsWindowDisplayAffinityMonitor(window);
                };

                window.SourceInitialized += handler;

                return handler;
            });

        private static EventHandler EnsureSourceInitializedHandler(Window window)
            => SourceInitializedWeakTable.GetValue(window, MakeLazySourceInitializedHandler).Value;

        #region IsWindowDisplayAffinityMonitor

        public static readonly DependencyProperty IsWindowDisplayAffinityMonitorProperty = DependencyProperty.RegisterAttached("IsWindowDisplayAffinityMonitor", typeof(bool), typeof(WindowsInterop),
            new PropertyMetadata
            {
                PropertyChangedCallback = (d, e) =>
                {
                    ApplyIsWindowDisplayAffinityMonitor(d as Window, (bool)e.NewValue);
                }
            });

        private static bool ApplyIsWindowDisplayAffinityMonitorCore(Window window, bool value)
        {
            var wih = new WindowInteropHelper(window);
            var handle = wih.Handle;
            if (handle == IntPtr.Zero)
                return false;

            if (value)
                EnsureHwndSourceHook(window);

            try
            {
                handle.SetWindowDisplayAffinity(value ? WindowDisplayAffinity.Monitor : WindowDisplayAffinity.None);
            }
            catch
            {
                window.SetIsWindowDisplayAffinityMonitor(handle.IsWindowDisplayAffinityMonitor());
            }
            return true;
        }
        private static void ApplyIsWindowDisplayAffinityMonitor(Window window, bool value)
        {
            if (window == null) return;

            if (!ApplyIsWindowDisplayAffinityMonitorCore(window, value))
                EnsureSourceInitializedHandler(window);
        }

        public static void SetIsWindowDisplayAffinityMonitor(this Window window, bool value)
        {
            if (window == null)
                throw new ArgumentNullException(nameof(window));

            window.SetValue(IsWindowDisplayAffinityMonitorProperty, value);
        }
        public static bool GetIsWindowDisplayAffinityMonitor(this Window window)
        {
            if (window == null)
                throw new ArgumentNullException(nameof(window));

            return (bool)window.GetValue(IsWindowDisplayAffinityMonitorProperty);
        }

        private static void RefreshIsWindowDisplayAffinityMonitor(Window window)
        {
            var isWindowDisplayAffinityMonitor = window.GetIsWindowDisplayAffinityMonitor();
            ApplyIsWindowDisplayAffinityMonitorCore(window, isWindowDisplayAffinityMonitor);
        }

        #endregion

        #region IsHitTestTransparent

        public static readonly DependencyProperty IsHitTestTransparentProperty = DependencyProperty.RegisterAttached("IsHitTestTransparent", typeof(bool), typeof(WindowsInterop),
            new PropertyMetadata
            {
                PropertyChangedCallback = (d, e) =>
                {
                    ApplyIsHitTestTransparent(d as Window, (bool)e.NewValue);
                }
            });

        private static bool ApplyIsHitTestTransparentCore(Window window, bool value)
        {
            var wih = new WindowInteropHelper(window);
            var handle = wih.Handle;
            if (handle == IntPtr.Zero)
                return false;

            handle.SetHitTestTransparent(value);
            return true;
        }
        private static void ApplyIsHitTestTransparent(Window window, bool value)
        {
            if (window == null) return;

            if (!ApplyIsHitTestTransparentCore(window, value))
                EnsureSourceInitializedHandler(window);
        }

        public static void SetIsHitTestTransparent(this Window window, bool value)
        {
            if (window == null)
                throw new ArgumentNullException(nameof(window));

            window.SetValue(IsHitTestTransparentProperty, value);
        }
        public static bool GetIsHitTestTransparent(this Window window)
        {
            if (window == null)
                throw new ArgumentNullException(nameof(window));

            return (bool)window.GetValue(IsHitTestTransparentProperty);
        }

        #endregion
    }
}
